//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

namespace Microsoft.Research.ScientificWorkflow.TridentComposer
{
    #region Using directives
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Globalization;
    using System.Text;
    using System.Windows.Media;
    using System.Xml;
    using Microsoft.Research.ScientificWorkflow.TridentModel;
    using Microsoft.Research.ScientificWorkflow.TridentUtilities; 
    #endregion

    /// <summary>
    /// Class for the dataypes functionality.
    /// </summary>
    public static class DataTypesComposer
    {
        /// <summary>
        /// Random generator.
        /// </summary>
        private static Random colorGenerator = new Random();

        /// <summary>
        /// Two XML files define the meta data associated with data types to
        /// be used by the workflow designer application. The first file is
        /// stored internally to the project and can not be edited by the user.
        /// The second is stored in the application directory under the 'config'
        /// directory in a file called 'DataTypes.xml'.
        /// 
        /// The internal file is read first and dictionary of models that can be
        /// looked up by the qualified workflowName of the type. Then the local file is
        /// checked, and if it exists and is well formatted, then it is added to
        /// the data created from the internal file.
        /// </summary>
        public static void ReadAllDataTypes(Dictionary<String, DataTypeModel> dictionary)
        {
            XmlDocument typeXml =
                 XmlUtilities.GetXmlResource(typeof(TridentUtilities.ActivityUtilities).Assembly,
                 "Microsoft.Research.ScientificWorkflow.TridentUtilities.Xml.DataTypes.xml");
            
            CreateDataTypesFromXml(typeXml, dictionary, true);
            
        }

        /// <summary>
        /// Update Keys for the dataypes.
        /// </summary>
        /// <param name="activityModel">Base Model.</param>
        /// <param name="dataTypes">Data Types.</param>
        /// <param name="inUseDataTypes">In used data types.</param>
        public static void UpdateDataKeys(BaseModel model, Dictionary<String, DataTypeModel> dataTypes, Dictionary<string, int> inUseDataTypes)
        {
            if (model != null)
            {
                if (!model.IsSubsectionModel)
                {
                    foreach (ParameterDescriptionModel input in model.InputParameters)
                    {
                        UpdateDataKey(input, dataTypes, inUseDataTypes);
                    }

                    foreach (ParameterDescriptionModel output in model.OutputParameters)
                    {
                        UpdateDataKey(output, dataTypes, inUseDataTypes);
                    }
                }

                if (model.IsCompositeActivityModel || model.IsSubsectionModel || model.IsCompositeBlackBoxModel)
                {
                    BaseCompositeModel compositeActModel = model as BaseCompositeModel;
                    foreach (BaseModel child in compositeActModel.Children)
                    {
                        UpdateDataKeys(child, dataTypes, inUseDataTypes);
                    }
                }
            }
        }

        /// <summary>
        /// Update key for the parameter.
        /// </summary>
        /// <param name="param">Parameter Description Model.</param>
        /// <param name="dataTypes">Data Types.</param>
        /// <param name="inUseDataTypes">In used data types.</param>
        private static void UpdateDataKey(ParameterDescriptionModel param, Dictionary<String, DataTypeModel> dataTypes, Dictionary<string, int> inUseDataTypes)
        {
            if (!inUseDataTypes.ContainsKey(param.DataType))
            {
                inUseDataTypes.Add(param.DataType, 1);
            }
            else
            {
                inUseDataTypes[param.DataType] += 1;
            }

            string id = param.DataType;
            if (!dataTypes.ContainsKey(id))
            {
                DataTypeModel dataModel = new DataTypeModel(id);
                dataModel.Name = id;
                dataTypes[id] = dataModel;
            }
        }

        /// <summary>
        /// Remove key from the in used data types for the activity.
        /// </summary>
        /// <param name="activityModel">Base Model.</param>
        /// <param name="dataTypes">Data Types.</param>
        /// <param name="inUseDataTypes">In used data types.</param>
        public static void RemoveDataKeys(BaseModel model, Dictionary<string, int> inUseDataTypes)
        {
            if (model != null)
            {
                if (!model.IsSubsectionModel)
                {
                    foreach (ParameterDescriptionModel input in model.InputParameters)
                    {
                        RemoveDataKey(input, inUseDataTypes);
                    }

                    foreach (ParameterDescriptionModel output in model.OutputParameters)
                    {
                        RemoveDataKey(output, inUseDataTypes);
                    }
                }

                if (model.IsCompositeActivityModel || model.IsSubsectionModel || model.IsCompositeBlackBoxModel)
                {
                    BaseCompositeModel compositeActModel = model as BaseCompositeModel;
                    foreach (BaseModel child in compositeActModel.Children)
                    {
                        RemoveDataKeys(child, inUseDataTypes);
                    }
                }
            }
        }

        /// <summary>
        /// Remove key from the in used data types based on the parameter.
        /// </summary>
        /// <param name="param">Parameter Description Model.</param>
        /// 
        /// <param name="inUseDataTypes">In used data types.</param>
        private static void RemoveDataKey(ParameterDescriptionModel param, Dictionary<string, int> inUseDataTypes)
        {
            if (inUseDataTypes.ContainsKey(param.DataType))
            {
                inUseDataTypes[param.DataType] -= 1;
                if (inUseDataTypes[param.DataType] <= 0)
                {
                    inUseDataTypes.Remove(param.DataType);
                }
            }
        }

        /// <summary>
        /// Creates datatypes from XML.
        /// </summary>
        /// <param name="xml"></param>
        /// <param name="dictionary"></param>
        /// <param name="isBuiltIn"></param>
        private static void CreateDataTypesFromXml(XmlDocument xml, Dictionary<String, DataTypeModel> dictionary, bool isBuiltIn)
        {
            CreateDataTypesFromXml(xml, dictionary, false, isBuiltIn);
        }

        /// <summary>
        /// Creates datatypes from XML.
        /// </summary>
        /// <param name="xml"></param>
        /// <param name="dictionary"></param>
        /// <param name="overwriteExisting"></param>
        /// <param name="isBuiltIn"></param>
        public static void CreateDataTypesFromXml(XmlDocument xml, Dictionary<String, DataTypeModel> dictionary, bool overwriteExisting, bool isBuiltIn)
        {
            XmlElement typeRoot = xml.DocumentElement;

            if (typeRoot != null)
            {
                Collection<XmlElement> typeElems =
                    XmlUtilities.GetChildElementsByTagName(typeRoot, "DataType");
                foreach (XmlElement typeElem in typeElems)
                {
                    DataTypeModel model = CreateDataTypeFromXml(typeElem);
                    model.IsBuiltIn = isBuiltIn;

                    if (model == null)
                        continue;

                    if (!overwriteExisting)
                    {
                        if (!dictionary.ContainsKey(model.Id))
                        {
                            dictionary[model.Id] = model;
                        }
                    }
                    else
                    {
                        dictionary[model.Id] = model;
                    }
                }
            }
        }

        /// <summary>
        /// Creates a XML document from the dictionary.
        /// </summary>
        /// <param name="dictionary"></param>
        /// <returns></returns>
        public static XmlDocument SaveCustomDataTypesToXml(Dictionary<string, DataTypeModel> dictionary)
        {
            XmlDocument datatypesXml = new XmlDocument();
            XmlElement baseElement = datatypesXml.CreateElement("DataTypes");
            datatypesXml.AppendChild(baseElement);

            foreach (DataTypeModel datatype in dictionary.Values)
            {
                if (!datatype.IsBuiltIn)
                {
                    XmlElement datatypeElement = CreateElementFromDatatype(datatype, datatypesXml);
                    baseElement.AppendChild(datatypeElement);
                }
            }

            return datatypesXml;
        }

        /// <summary>
        /// Create a DataTypeModel from the 'DataType' XmlElement
        /// </summary>
        /// <param name="elem"></param>
        /// <returns></returns>
        private static DataTypeModel CreateDataTypeFromXml(XmlElement elem)
        {
            if (elem == null)
                return null;

            // Read the values associated with the element data type
            string id = elem.GetAttribute("ID");
            if (String.IsNullOrEmpty(id))
            {
                throw new ArgumentException("Found a data type definition with no ID specified");
            }

            DataTypeModel result = new DataTypeModel(id);
            result.Name = elem.GetAttribute("Name");
            result.Label = elem.GetAttribute("Label");
            result.Description = elem.GetAttribute("Description");
            string colorName = elem.GetAttribute("EditorColor");

            try
            {
                if (!String.IsNullOrEmpty(colorName))
                    result.PrimaryEditorColor = (Color)ColorConverter.ConvertFromString(colorName);
            }
            catch (FormatException)
            {
                // ignore the exception.
            }

            return result;
        }

        /// <summary>
        /// Create a XMl element from the datatypemodel.
        /// </summary>
        /// <param name="datatype">The datatype model.</param>
        /// <returns>XML element with the model info.</returns>
        private static XmlElement CreateElementFromDatatype(DataTypeModel datatype, XmlDocument root)
        {
            XmlElement element = root.CreateElement("DataType");
                        
            element.SetAttribute("ID",datatype.Id);
            
            element.SetAttribute("Name", datatype.Name);

            element.SetAttribute("Description", datatype.Description);

            element.SetAttribute("EditorColor", datatype.PrimaryEditorColor.ToString());
         
            return element;
        }

        /// <summary>
        /// Generate a dark random color. 
        /// </summary>
        /// <returns></returns>
        public static Color GenerateRandomColor()
        {
            int redInt, greenInt, blueInt;

            // Initialize the color string.
            StringBuilder colorString = new StringBuilder("#FF");

            // Get the red channel.
            redInt = colorGenerator.Next(200);
            colorString.Append(redInt.ToString("X2", CultureInfo.CurrentUICulture));

            // Get the green channel.
            greenInt = colorGenerator.Next(200);
            colorString.Append(greenInt.ToString("X2", CultureInfo.CurrentUICulture));

            // Get the blue channel.
            blueInt = colorGenerator.Next(200);
            colorString.Append(blueInt.ToString("X2", CultureInfo.CurrentUICulture));

            return (Color)ColorConverter.ConvertFromString(colorString.ToString());
        }

        /// <summary>
        /// Returns the friendly name of the type.
        /// </summary>
        /// <param name="datatypeName">The fully qualified name of the type.</param>
        /// <returns>The friendly name.</returns>
        public static string GetFriendlyDataTypeName(string dataTypeName)
        {
            if (string.IsNullOrEmpty(dataTypeName))
            {
                throw new ArgumentNullException("dataTypeName");
            }
            string result = dataTypeName;

            // Truncate the string if it contains a [
            int indexOfBraces = result.IndexOf('[');
            if (indexOfBraces > 0)
            {
                result = result.Remove(indexOfBraces);
            }

            // Truncate the string if it contains a +
            int indexOfPlus = result.IndexOf('+');
            if (indexOfPlus > 0)
            {
                result = result.Substring(indexOfPlus + 1);
            }

            // Truncate the string if it contains a '
            int indexOfHyphen = result.IndexOf('`');
            if (indexOfHyphen > 0)
            {
                result = result.Remove(indexOfHyphen);
            }

            // Remove all the namespaces.
            int lastIndexOfDot = result.LastIndexOf('.');
            if (lastIndexOfDot > 0)
            {
                result = result.Substring(lastIndexOfDot + 1);
            }
            return result;
        }
    }
}
